const fs = require('fs');
const readline = require('readline');
let QuadTree = require('./QuadTree')
let Point = require('./models/Point')
let Vector = require('./models/Vector')
let Node = require('./models/PathNode')

/**
 * 路径规划类
 */
class PathPlan {
    constructor() {
        this.map = {} // 图数据 { pointId : {to: [], from: [], point: null} }
        this.pointIndex = new QuadTree() // 四叉树索引
    }

    /**
     * 初始化，读取地图数据
     * @param mapFile 图数据
     * @param nodeFile 点的经纬度数据
     * @returns {Promise<void>}
     */
    async init(mapFile, nodeFile) {
        await this._readMap(mapFile)
        await this._readNode(nodeFile)
        // 验证数据：图数据的节点，都有经纬度信息，否则报错
        for (let id in this.map) {
            if (this.map[id].point === null) {
                throw new Error(`数据错误，没有node数据，id:${id}`)
            }
        }
        console.log('地图数据读取完毕')
    }

    /**
     * 读取图数据
     * @param file 图数据csv
     */
    _readMap(file) {
        return new Promise((resolve) => {
            const rl = readline.createInterface({
                input: fs.createReadStream(file),
                output: process.stdout,
                terminal: false
            });
            rl.on('line', (line) => {
                let [p1, p2] = line.split(',').map(item => parseInt(item))
                if (!this.map[p1]) {
                    this.map[p1] = {to: [], from: [], point: null}
                }
                if (!this.map[p2]) {
                    this.map[p2] = {to: [], from: [], point: null}
                }
                this.map[p1].to.push(p2)
                this.map[p2].from.push(p1)
            });
            rl.on('close', () => {
                console.log('map读取完成')
                resolve()
            });
        })
    }

    /**
     * 读取节点经纬度数据
     * @param file 节点经纬度数据csv
     */
    _readNode(file) {
        return new Promise((resolve) => {
            const rl = readline.createInterface({
                input: fs.createReadStream(file),
                output: process.stdout,
                terminal: false
            });
            rl.on('line', (line) => {
                let [nodeId, lat, lng] = line.split(',')
                nodeId = parseInt(nodeId)
                // 只保存图数据中的点，node数据中可能包含非道路数据
                if (this.map[nodeId]) {
                    let p = new Point(parseFloat(lng), parseFloat(lat), nodeId)
                    this.map[nodeId].point = p
                    this.pointIndex.insert(p)
                }
            });
            rl.on('close', () => {
                console.log('node读取完成')
                resolve()
            });
        })
    }

    /**
     * 路径规划
     * @param startPoint Point 起始点
     * @param endPoint Point 目的地
     * @returns {msg: string, status: string, path: Array, distance: number, use_time: number}
     */
    planning(startPoint, endPoint) {
        try {
            let ts = new Date().getTime()
            let p1 = this.findNearestPoint(startPoint)
            let planStart = p1.id ? p1 : p1.to
            let p2 = this.findNearestPoint(endPoint)
            let planEnd = p2.id ? p2 : p2.from
            let result = this._planningById(planStart.id, planEnd.id)
            if (!p1.id) {
                delete p1.to
                delete p1.from
                result.path.unshift(p1)
                result.distance += p1.distance(planStart)
            }
            if (!p2.id) {
                delete p2.to
                delete p2.from
                result.path.push(p2)
                result.distance += p2.distance(planEnd)
            }
            result['use_time'] = new Date().getTime() - ts
            result['status'] = 'success'
            return result
        } catch (err) {
            return {
                status: 'error',
                msg: err.toString()
            }
        }
    }

    /**
     * 起始点绑路
     * @param origin Point 原点
     * @returns Point 最近的点，如果id为0，则是根据图数据补充的中间点，如果id不为0，则是图数据中的点
     */
    findNearestPoint(origin) {
        // 查询附近的10个点
        let arr = this.pointIndex.findNearestPoint(origin, 10)
        // 根据这些点相关的向量数据，补全的中间点
        let pointList = []
        for (let p of arr) {
            if (!this.map[p.id]) {
                continue // 图数据中没有这个点
            }
            pointList.push(p) // 添加当前点
            for (let pId of this.map[p.id].to) {
                if (this.map[pId].point) {
                    pointList.push(this.map[pId].point) // 添加当前点可到达的点
                    let v = new Vector(p, this.map[pId].point)
                    pointList.push(...v.resample()) // 添加补全的点
                }
            }
            for (let pId of this.map[p.id].from) {
                if (this.map[pId].point) {
                    pointList.push(this.map[pId].point) // 添加可到达当前点的点
                    let v = new Vector(this.map[pId].point, p)
                    pointList.push(...v.resample()) // 添加补全的点
                }
            }
        }

        // 将这些点创建用四叉树索引，再查找最近的点
        let quadTree = new QuadTree()
        for (let p of pointList) {
            quadTree.insert(p)
        }
        let nearPoints = quadTree.findNearestPoint(origin, 1)
        return nearPoints[0] // 返回最近的点
    }

    /**
     * 使用点id路径规划
     * @param startId number 起点id
     * @param endId number 终点id
     * @returns {{path: [Point], distance: number}}
     */
    _planningById(startId, endId) {
        let startPoint = this.map[startId].point // 起点
        let endPoint = this.map[endId].point // 终点
        let endNode = new Node(endPoint, null, null) // 终点Node
        let startNode = new Node(startPoint, null, endNode) // 起点Node
        let openList = [startNode] // 待遍历Node的列表
        let openMap = {[startId]: startNode} // 待遍历Node的Map
        let closeSet = new Set([]) // 已遍历的pointId
        while (true) {
            if (openList.length === 0) {
                throw new Error('路径规划失败')
            }
            // 取最小预估代价的点
            let minF = Number.MAX_VALUE
            let currentIndex = 0;
            for (let i = 0; i < openList.length; i++) {
                if (openList[i].f < minF) {
                    minF = openList[i].f
                    currentIndex = i
                }
            }
            // 把找出来的点，从openList和openMap中删除
            let currentNode = openList.splice(currentIndex, 1)[0]
            delete openMap[currentNode.point.id]
            // 添加到closeSet
            closeSet.add(currentNode.point.id)

            if (this.map[currentNode.point.id]) {
                for (let itemId of this.map[currentNode.point.id].to) {
                    if (closeSet.has(itemId)) {
                        continue
                    }
                    if (openMap[itemId]) {
                        openMap[itemId].updateParent(currentNode)
                    } else {
                        let p = this.map[itemId].point
                        let node = new Node(p, currentNode, endNode)
                        if (itemId === endId) {
                            return {
                                path: node.getPath(),
                                distance: node.g
                            }
                        }
                        openList.push(node)
                        openMap[itemId] = node
                    }
                }
            }
        }
    }
}


module.exports = PathPlan

